index.tsx 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. "use client";
  2. import { loginApi, registerApi } from "@/api/login";
  3. import CustomButton from "@/components/CustomButton";
  4. import MobileField from "@/components/Fields/MobileField";
  5. import { useEventPoint } from "@/hooks/useEventPoint";
  6. import { Link, useRouter } from "@/i18n/routing";
  7. import { useSystemStore } from "@/stores/useSystemStore";
  8. import { useUserInfoStore } from "@/stores/useUserInfoStore";
  9. import { emailReg, neReg } from "@/utils";
  10. import { setCookies } from "@/utils/Cookies";
  11. import { Local } from "@/utils/storage";
  12. import {
  13. Checkbox,
  14. DatePicker,
  15. DatePickerRef,
  16. Form,
  17. Input,
  18. Radio,
  19. Space,
  20. TextArea,
  21. Toast,
  22. } from "antd-mobile";
  23. import { PickerDate } from "antd-mobile/es/components/date-picker/util";
  24. import clsx from "clsx";
  25. import dayjs from "dayjs";
  26. import { useTranslations } from "next-intl";
  27. import { useSearchParams } from "next/navigation";
  28. import { FC, RefObject, useEffect, useRef, useState } from "react";
  29. import styles from "./style.module.scss";
  30. interface MobileFieldProps {
  31. value?: string;
  32. onChange?: (value: string) => void;
  33. }
  34. // const MobileField: FC<MobileFieldProps> = (props) => {
  35. // const t = useTranslations("form");
  36. // const { value, onChange } = props;
  37. //
  38. // const changeHandler = (value: string) => {
  39. // let newAmount = value.replace(/[^0-9]/g, "");
  40. // if (onChange) {
  41. // onChange(newAmount);
  42. // }
  43. // };
  44. // return (
  45. // <>
  46. // <Space align="center" className={"text-[#ccc]"}>
  47. // <Space align="center">+55</Space>
  48. // <Input
  49. // placeholder={t("phone")}
  50. // maxLength={11}
  51. // value={value}
  52. // onChange={(e) => changeHandler(e)}
  53. // />
  54. // </Space>
  55. // </>
  56. // );
  57. // };
  58. const SexMobile: FC<MobileFieldProps> = (props) => {
  59. const { value, onChange } = props;
  60. const handler = () => {
  61. console.log(`🚀🚀🚀🚀🚀-> in index.tsx on 58`, 123);
  62. };
  63. return (
  64. <div className={"w-[100%] border-4"} onClick={handler}>
  65. <Space align="center" className={"border-4 text-[#ccc]"}>
  66. <div className={"h-full w-[100%]"}>{value}</div>
  67. </Space>
  68. </div>
  69. );
  70. };
  71. interface FormProps {
  72. /**
  73. * 通讯地址
  74. */
  75. address?: string;
  76. /**
  77. * 用户头像
  78. */
  79. avatar_url?: string;
  80. /**
  81. * 生日:yyyy-MM-dd
  82. */
  83. birthday?: string;
  84. /**
  85. * 邮箱地址
  86. */
  87. email?: string;
  88. /**
  89. * 真实姓名
  90. */
  91. nick_name?: string;
  92. /**
  93. * 外部ID
  94. */
  95. open_id?: string;
  96. /**
  97. * 身份护照
  98. */
  99. passport?: string;
  100. /**
  101. * 用户密码
  102. */
  103. pwd: string;
  104. /**
  105. * 推荐码
  106. */
  107. referrer_code?: string;
  108. /**
  109. * 用户名
  110. */
  111. user_name?: string;
  112. /**
  113. * 用户电话号码
  114. */
  115. user_phone: string;
  116. /**
  117. * 用户类型
  118. */
  119. user_type?: string;
  120. sex?: number;
  121. /**
  122. * 渠道链接
  123. */
  124. channel_code?: string;
  125. code_phone?: string;
  126. first_name?: string;
  127. last_name?: string;
  128. }
  129. interface FormInitStateTypes extends FormProps {
  130. mobile?: {
  131. preValue: string;
  132. realValue: string;
  133. };
  134. }
  135. export type FormType = "login" | "register";
  136. interface Props {
  137. type?: FormType;
  138. isCanSubmit?: boolean;
  139. }
  140. const FormComponent: FC<Props> = (props) => {
  141. const { type = "register", isCanSubmit = true } = props;
  142. const isStrictMode = useSystemStore().identity_verify.register === 1;
  143. const { setUserInfo } = useUserInfoStore();
  144. const t = useTranslations();
  145. const searchParams = useSearchParams();
  146. const [statusText, setStatusText] = useState("");
  147. const { eventLogin, eventRegister } = useEventPoint();
  148. const formRef = useRef<any>(null);
  149. /// 密码可见
  150. const [visible, setVisible] = useState(false);
  151. const [isRemeber, setIsRemeber] = useState(true);
  152. const spanClassName = clsx("iconfont", {
  153. "icon-kejian": visible,
  154. "icon-bukejian": !visible,
  155. });
  156. // 是否同意协议
  157. const [checkBoxValue, setCheckBoxValue] = useState<boolean>(false);
  158. const router = useRouter();
  159. /// 初始值
  160. const params = useRef<FormInitStateTypes>({
  161. user_phone: "",
  162. mobile: { preValue: "55", realValue: "" },
  163. pwd: "",
  164. sex: 0,
  165. address: undefined,
  166. birthday: undefined,
  167. email: undefined,
  168. passport: undefined,
  169. referrer_code: undefined,
  170. });
  171. useEffect(() => {
  172. formRef.current?.resetFields();
  173. if (type === "login") {
  174. setTimeout(() => {
  175. const cacheLogin = Local.getKey("ban_cache_login");
  176. if (cacheLogin) {
  177. formRef.current?.setFieldsValue({
  178. pwd: cacheLogin.pwd,
  179. mobile: {
  180. preValue: "55",
  181. realValue: cacheLogin.user_phone.replace(/^\d{2}/, ""),
  182. },
  183. });
  184. }
  185. }, 100);
  186. }
  187. }, [type]);
  188. const onFinish = (values: FormInitStateTypes) => {
  189. if (!isCanSubmit) return;
  190. const { mobile } = values;
  191. const newValue = {
  192. ...values,
  193. user_phone: `${mobile?.preValue}${mobile?.realValue}`,
  194. code_phone: mobile?.preValue,
  195. };
  196. delete newValue.mobile;
  197. if (isStrictMode) {
  198. strictHandler(newValue as FormProps);
  199. } else {
  200. looseHandler(newValue as FormProps);
  201. }
  202. };
  203. /// 严格模式
  204. const strictHandler = (values: FormProps) => {
  205. if (type !== "login") {
  206. if (!checkBoxValue) {
  207. Toast.show({
  208. content: t("form.readyAgreement"),
  209. });
  210. return;
  211. }
  212. values.birthday = dayjs(values.birthday).format("YYYY/MM/DD");
  213. }
  214. looseHandler(values);
  215. };
  216. const loginHandler = async (values: FormProps) => {
  217. return new Promise(async (resolve, reject) => {
  218. const loginResult = await loginApi(values).catch((error) => {
  219. let text = error ? t(`code.${error.data.code}`) : t(`code.${500}`);
  220. Toast.show({
  221. content: text,
  222. });
  223. });
  224. if (loginResult?.code === 200) {
  225. if (type === "login") {
  226. if (isRemeber) {
  227. Local.setKey("ban_cache_login", {
  228. user_phone: values.user_phone,
  229. pwd: values.pwd,
  230. });
  231. } else {
  232. Local.removeKey("ban_cache_login");
  233. }
  234. }
  235. sessionStorage.removeItem("ShowProxy");
  236. sessionStorage.removeItem("dialogShow");
  237. eventLogin();
  238. setCookies("Token", loginResult.data.token as string);
  239. setUserInfo(loginResult.data);
  240. resolve(loginResult);
  241. return loginResult;
  242. } else {
  243. reject();
  244. }
  245. });
  246. };
  247. /// 宽松模式
  248. const looseHandler = async (values: FormProps) => {
  249. // 请求
  250. Toast.show({
  251. icon: "loading",
  252. duration: 0,
  253. });
  254. // 注册
  255. if (type === "register") {
  256. values.user_name = `${values.first_name} ${values.last_name}`;
  257. delete values.first_name;
  258. delete values.last_name;
  259. const newValues = {
  260. ...values,
  261. referrer_code: sessionStorage.getItem("shareId") ?? undefined,
  262. channel_code: Local.getKey("channel_code") ?? undefined,
  263. // 轮盘邀请
  264. turntable_id: Number(sessionStorage.getItem("turntable_id")) ?? undefined,
  265. turntable_user_id: Number(sessionStorage.getItem("turntable_user_id")) ?? undefined,
  266. turntable_time: Number(sessionStorage.getItem("turntable_time")) ?? undefined,
  267. // 快玩id
  268. click_id: Local.getKey("ban_click_id") ?? undefined,
  269. };
  270. registerApi(newValues)
  271. .then(async (res) => {
  272. if (res.code === 200) {
  273. eventRegister();
  274. loginHandler({
  275. pwd: values.pwd,
  276. user_phone: values.user_phone,
  277. code_phone: values.code_phone,
  278. }).then(() => {
  279. router.replace("/");
  280. Toast.clear();
  281. });
  282. }
  283. })
  284. .catch((error) => {
  285. console.log(`🚀🚀🚀🚀🚀-> in index.tsx on 257`, error);
  286. if (error.data?.code === 1017) {
  287. sessionStorage.removeItem("shareId");
  288. }
  289. Toast.show({
  290. content: t(`code.${error.data?.code ?? 500}`),
  291. });
  292. });
  293. } else {
  294. /// 登录
  295. loginHandler(values).then(() => {
  296. Toast.clear();
  297. const redirect = searchParams.get("redirect")
  298. ? `/${searchParams.get("redirect")}`
  299. : "/";
  300. router.replace(redirect);
  301. });
  302. }
  303. };
  304. const onConfirm = (value: PickerDate) => {
  305. const isChildren = dayjs().subtract(18, "year").isBefore(value);
  306. if (isChildren) {
  307. Toast.show({
  308. icon: "fail",
  309. content: t("form.NotSuitableForChildren"),
  310. });
  311. }
  312. };
  313. const ageValidator = (rule: any, value: PickerDate) => {
  314. const isChildren = dayjs().subtract(18, "year").isBefore(value);
  315. if (isChildren) {
  316. return Promise.reject(new Error(rule.message));
  317. } else {
  318. return Promise.resolve();
  319. }
  320. };
  321. // 手机号验证规则
  322. const checkMobile = (_: any, value: any) => {
  323. const reg = /^.{2}9/;
  324. if (!reg.test(value.realValue)) {
  325. return Promise.reject(new Error(t("form.phoneRules")));
  326. } else if (value.realValue.length < 10) {
  327. return Promise.reject(new Error(t("form.phoneMinReg")));
  328. } else {
  329. return Promise.resolve();
  330. }
  331. };
  332. return (
  333. <div className={clsx(styles.loginForm, "custom-form !px-[0]")}>
  334. <Form
  335. style={{
  336. "--border-bottom": "none",
  337. "--border-top": "none",
  338. "--border-inner": "none",
  339. }}
  340. ref={formRef}
  341. initialValues={params.current}
  342. onFinish={onFinish}
  343. footer={
  344. <CustomButton type="primary" className={clsx("w-[100%] font-bold")}>
  345. {type === "login" ? t("form.loginText") : t("form.registerText")}
  346. </CustomButton>
  347. }
  348. >
  349. <Form.Item name="mobile" rules={[{ required: true, validator: checkMobile }]}>
  350. <MobileField />
  351. </Form.Item>
  352. <Form.Item
  353. name="pwd"
  354. label={
  355. <i className="iconfont icon-xiugaimima text-[.18rem] text-[#465461]"></i>
  356. }
  357. extra={
  358. <span className={spanClassName} onClick={() => setVisible(!visible)}></span>
  359. }
  360. layout="horizontal"
  361. rules={[
  362. { required: true, message: t("form.passwordReg") },
  363. { min: 6, max: 20, message: t("form.passwordMinReg") },
  364. ]}
  365. >
  366. <Input
  367. placeholder={t("form.password")}
  368. maxLength={20}
  369. type={visible ? "text" : "password"}
  370. />
  371. </Form.Item>
  372. {type === "login" && (
  373. <div
  374. className="mb-[.3rem] flex items-center"
  375. onClick={() => {
  376. setIsRemeber(!isRemeber);
  377. }}
  378. >
  379. {/* <i
  380. className={clsx("iconfont mr-[.1rem] text-[.16rem] text-[#ff5a00]", {
  381. "icon-xuanzhong": isRemeber,
  382. "icon-gouxuan-weixuanzhong-xianxingfangkuang": !isRemeber,
  383. })}
  384. ></i> */}
  385. <div className="mr-[.1rem] flex h-[.16rem] !w-[.16rem] items-center justify-center border-[1px] border-[#ff5a00]">
  386. {isRemeber && <i className="iconfont icon-gou text-[#ff5a00]"></i>}
  387. </div>
  388. <span className="text-[.12rem]">Lembrar a senha</span>
  389. </div>
  390. )}
  391. {type !== "login" && isStrictMode ? (
  392. <>
  393. {/* <Form.Item
  394. name="user_name"
  395. label=""
  396. rules={[{ required: true, message: t("form.usernameReg") }]}
  397. >
  398. <Input placeholder={t("form.username")} />
  399. </Form.Item> */}
  400. <div className="flex items-center">
  401. <Form.Item
  402. name="first_name"
  403. className="mr-[.1rem] w-[1.4rem]"
  404. layout="horizontal"
  405. label={
  406. <i className="iconfont icon-yonghu1 text-[.18rem] leading-[1] text-[#465461]"></i>
  407. }
  408. rules={[{ required: true, message: t("form.usernameReg") }]}
  409. >
  410. <Input placeholder={"Insira seu nome"} />
  411. </Form.Item>
  412. <Form.Item
  413. className="flex-1"
  414. name="last_name"
  415. label=""
  416. rules={[{ required: true, message: t("form.usernameReg") }]}
  417. >
  418. <Input placeholder={"Insira seu sobrenome"} />
  419. </Form.Item>
  420. </div>
  421. <Form.Item
  422. name="birthday"
  423. clickable={false}
  424. trigger={"onConfirm"}
  425. arrowIcon={<i className={"iconfont icon-xiangyou1"}></i>}
  426. onClick={(e, datePickerRef: RefObject<DatePickerRef>) => {
  427. datePickerRef.current?.open();
  428. }}
  429. rules={[
  430. { required: true, message: t("form.birthdayReg") },
  431. {
  432. message: t("form.NotSuitableForChildren"),
  433. validator: ageValidator,
  434. },
  435. ]}
  436. >
  437. <DatePicker
  438. getContainer={null}
  439. min={dayjs().subtract(50, "year").toDate()}
  440. max={dayjs().toDate()}
  441. style={{ background: "#fff", color: "#000" }}
  442. >
  443. {(value) =>
  444. value ? (
  445. dayjs(value).format("YYYY/MM/DD")
  446. ) : (
  447. <span className={"text-[#ccc]"}>{t("form.birthday")}</span>
  448. )
  449. }
  450. </DatePicker>
  451. </Form.Item>
  452. <Form.Item
  453. name="email"
  454. layout="horizontal"
  455. label={
  456. <i className="iconfont icon-youjian text-[.13rem] leading-[1] text-[#465461]"></i>
  457. }
  458. rules={[
  459. { required: true, message: t("form.emailReg"), pattern: emailReg },
  460. ]}
  461. >
  462. <Input placeholder={t("form.email")} />
  463. </Form.Item>
  464. <Form.Item name="sex">
  465. <Radio.Group>
  466. <Radio
  467. value={0}
  468. className={"mr-[0.1rem]"}
  469. style={{ "--icon-size": "18px" }}
  470. >
  471. {t("form.sexMan")}
  472. </Radio>
  473. <Radio value={1} style={{ "--icon-size": "18px" }}>
  474. {t("form.sexWoman")}
  475. </Radio>
  476. </Radio.Group>
  477. </Form.Item>
  478. <Form.Item
  479. name="passport"
  480. layout="horizontal"
  481. label={
  482. <i className="iconfont icon-shenfengzheng text-[.18rem] leading-[1] text-[#465461]"></i>
  483. }
  484. rules={[{ required: true, message: t("form.cardReg"), pattern: neReg }]}
  485. >
  486. <Input placeholder={t("form.card")} maxLength={11} type={"text"} />
  487. </Form.Item>
  488. <Form.Item
  489. name="address"
  490. label=""
  491. rules={[{ required: true, message: t("form.addressReg") }]}
  492. >
  493. <TextArea placeholder={t("form.address")} maxLength={40} />
  494. </Form.Item>
  495. <div className={"flex px-[0.1rem]"}>
  496. <Checkbox
  497. block
  498. style={{ "--icon-size": "16px" }}
  499. checked={checkBoxValue}
  500. onChange={(value) => setCheckBoxValue(value)}
  501. ></Checkbox>
  502. <div className={"ml-[10px] select-none break-all text-[12px]"}>
  503. {t("form.agreement")}
  504. <Link href={"/preventLaunderMoney"} className={"text-[#11de68]"}>
  505. {t("form.moneyAgreement")}
  506. </Link>
  507. <Link href={"/terms"} className={"text-[#11de68]"}>
  508. {t("form.serverAgreement")}
  509. </Link>
  510. {t("form.agreementAnd")}
  511. <Link href={"/gamingPolicy"} className={"text-[#11de68]"}>
  512. {t("form.childrenAgreement")}
  513. </Link>
  514. </div>
  515. </div>
  516. </>
  517. ) : null}
  518. {type !== "login" && (
  519. <div className={styles.gift}>
  520. <div className="relative flex items-center justify-center rounded-[14px] bg-[#ed4d32] px-[5px] py-[2px]">
  521. <div className="relative">
  522. <img
  523. style={{ animation: `Rotate 3s linear infinite` }}
  524. src="/prefile/bouncebg.webp"
  525. alt=""
  526. />
  527. <img
  528. className="absolute left-[50%] top-[50%] h-[14px] w-[14px] -translate-x-[50%] -translate-y-[50%] transform"
  529. src="/prefile/bounce.webp"
  530. alt=""
  531. />
  532. </div>
  533. <div className="pr-[5px] text-[14px] font-bold text-[yellow]">
  534. Registrar +R$ 3 ~ 77
  535. </div>
  536. <div className="absolute -bottom-[.04rem] left-[50%] h-[.08rem] w-[.08rem] rotate-[45deg] transform bg-[#ed4d32]"></div>
  537. </div>
  538. </div>
  539. )}
  540. </Form>
  541. </div>
  542. );
  543. };
  544. export default FormComponent;